using Microsoft.Isam.Esent.Interop;
using System;

namespace Microsoft.Isam.Esent.Serialization.Attributes
{
	/// <summary>This attribute marks the object as the ESE record.</summary>
	/// <remarks>Applying this attribute to a class declares a new table in the ESE database,
	/// with its schema suitable for storing instances of the record class.<br />
	/// Table columns are defined by 'Ese*Attribute' attributes applied to the properties and/or fields of the record class.<br />
	/// Table indices are defined by 'EseIndexAttribute' (or derived classes) attributes applied to the record class.</remarks>
	[AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false )]
	public sealed class EseTableAttribute : Attribute, iNotifyTableChanged
	{
		/// <summary>The name of the ESE table</summary>
		public readonly string tableName;

		/// <summary>Initialize with the default table name, which is the name of the class.</summary>
		public EseTableAttribute()
		{
			tableName = null;
		}

		/// <summary>Initialize with non-default table name.</summary>
		public EseTableAttribute( string strTableName )
		{
			tableName = strTableName;
		}

		// INotifyCollectionChanged implementation.

		// TODO [low]: when possible, merge undelivered events together. E.g. merge several NotifyTableChangedAction.Reset notifications into the single Reset. For some operations (e.g. "Add 10M records with Cursor<>.AddRange calls, 10 records per each transaction.") this will dramatically improve performance.

		/// <summary>This event is fired when the table data has changed.</summary>
		public event NotifyTableChangedHandler TableChanged;

		static System.Windows.Threading.Dispatcher s_EventsDispatcher = null;
		/// <summary>Call this static method to set the thread that will receive the table change notifications.</summary>
		/// <remarks>Please note that in the current implementation, the event dispatcher mechanism has severe design flaws.
		/// It fires event too early, i.e. before the transaction is committed, i.e. when another thread receives a notification, the new data is still not available to him until the source thread commits the top-level transaction.
		/// Moreover, if the transaction is rolled back instead of being committed, the events are still fired.
		/// If I were you, I wouldn't use this mechanism at all.</remarks>
		public static void setEventsDispatcher() { s_EventsDispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; }

		/// <summary>This constant is set to true for better data safety.
		/// We invoke event handlers asynchronously, even on the same thread.
		/// This is done to allow the transaction to commit, even if an exception is thrown from the event handler.</summary>
		const bool bAsyncInvokeEvenOnSameThread = true;

		/// <summary>Fire the TableChanged event.</summary>
		/// <param name="objSender"></param>
		/// <param name="ntcArgs"></param>
		public void onTableChanged( object objSender, NotifyTableChangedArgs ntcArgs )
		{
			if( null == TableChanged )
			{
				// No event handler was set => do nothing
				return;
			}

			if( null == s_EventsDispatcher )
			{
				// No EventTargetThread is set => raise synchronously
				TableChanged( objSender, ntcArgs );
				return;
			}

			if( !bAsyncInvokeEvenOnSameThread &&
				s_EventsDispatcher.Thread.ManagedThreadId == System.Threading.Thread.CurrentThread.ManagedThreadId )
			{
				// EventTargetThread was set, however the event was risen on the same thread => raise immediately
				TableChanged( objSender, ntcArgs );
				return;
			}

			// EventTargetThread was set, and the event is risen on another thread => raise asynchronously
			s_EventsDispatcher.BeginInvoke( TableChanged, objSender, ntcArgs );
		}
	}
}